package com.sztz.excel;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.RowTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.metadata.holder.ReadSheetHolder;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
 * 不规则的Excel解析处理器
 * @author yzy
 * @date 2022/7/14 17:56
 */
@Slf4j
public class MyExcelListener extends AnalysisEventListener<Map<Integer,Object>> {
    /**
     * 每批次最大处理行数
     */
    private int batchCount;
    /**
     * 出现异常是否继续读取下一行
     */
    private Boolean exceptionRead ;
    /**
     * 是否需要读取下一行
     */
    private Boolean hasNext ;
    /**
     * 允许的最大连续空行数。默认5行
     */
    private int maxAllowEmptyRowNum ;
    /**
     * 允许最大读多少行
     */
    private int maxReadRowNum;
    /**
     *创建list集合封装最终的数据
     */
    private List<Map<Integer,Object>> dataList ;
    /**
     * 解析数据的具体处理方法
     */
    private Consumer<List<Map<Integer,Object>>> consumer;
    /**
     * 使用建造器模式
     * @param builder 建造器
     * @author yzy
     * @date 2022/7/19 19:34
     */
    private MyExcelListener(Builder builder){
        this.consumer = builder.consumer;
        this.batchCount = builder.batchCount;
        this.exceptionRead = builder.exceptionRead;
        this.hasNext = builder.hasNext;
        this.maxAllowEmptyRowNum = builder.maxAllowEmptyRowNum;
        this.maxReadRowNum = builder.maxReadRowNum;
        this.dataList = new LinkedList<>();
    }
    /**
     *  建造器模式生成Excel处理器
     * @author yzy
     * @date 2022/7/14 17:56
     */
    public static class Builder{
        //默认最大处理行数1000行
        private int batchCount = 1000;
        //默认出现异常停止读取下一行
        private Boolean exceptionRead = Boolean.FALSE;
        //默认是一直读下去，直到读取到结束标识
        private Boolean hasNext = Boolean.TRUE;
        //默认允许最大连续5个空行，超过就停止解析
        private int maxAllowEmptyRowNum = 5;
        //默认允许最多读取3000行
        private int maxReadRowNum = 3000;
        //调用方需要定义自已的数据处理逻辑
        private Consumer<List<Map<Integer,Object>>> consumer;

        public Builder(Consumer<List<Map<Integer,Object>>> consumer){
            this.consumer = consumer;
        }

        public Builder setBatchCount(int batchCount){
            this.batchCount = batchCount;
            return this;
        }
        public Builder exceptionRead(Boolean exceptionRead){
            this.exceptionRead = exceptionRead;
            return this;
        }
        public Builder hasNext(Boolean hasNext){
            this.hasNext = hasNext;
            return this;
        }
        public Builder setMaxAllowEmptyRowNum(int maxAllowEmptyRowNum){
            this.maxAllowEmptyRowNum = maxAllowEmptyRowNum;
            return this;
        }
        public Builder setMaxReadRowNum(int maxReadRowNum){
            this.maxReadRowNum = maxReadRowNum;
            return this;
        }

        /**
         * 通过建造器的建造方法来生成Excel解析处理器
         */
        public MyExcelListener build(){
            return new MyExcelListener(this);
        }
    }

    /**
     * 解析到的数据处理
     * @param data 解析出的数据
     * @param context 解析上下文
     * @author yzy
     * @date 2022/7/14 17:56
     */
    @Override
    public void invoke(Map<Integer,Object> data, AnalysisContext context) {
        //手动对空行进行处理，否则会一直读取空行
        context.readWorkbookHolder().setIgnoreEmptyRow(Boolean.FALSE);
        if(!RowTypeEnum.EMPTY.equals(context.readRowHolder().getRowType())) {
            dataList.add(data);
            if (batchCount <= dataList.size()) {
                log.info("共解析到{}行数据",dataList.size());
                consumer.accept(dataList);
                dataList.clear();
            }
        }
    }

    /**
     * 最后剩下的数据处理
     * @param context 解析上下文
     * @author yzy
     * @date 2022/7/14 17:56
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if(CollUtil.isNotEmpty(dataList)){
            log.info("最后解析到{}行数据",dataList.size());
            consumer.accept(dataList);
        }
        log.info("所有数据解析完成！");
    }
    /**
     * 解析出现异常
     * @param exception 异常情况
     * @param context 解析上下文
     * @author yzy
     * @date 2022/7/19 17:10
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        ReadSheetHolder readSheetHolder = context.readSheetHolder();
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("数据转换异常,第{}行，第{}列解析异常，数据为:{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
        //记录解析出错的信息，后续可以返回出去
        String errorMsg = StrUtil.format("{}, Sheet {} 的第 {} 行解析失败，单元格数据为{}, 行数据为{}，异常信息为 : {}",exceptionRead ? "继续解析" : "停止解析", readSheetHolder.getSheetNo(), readSheetHolder.getRowIndex(),JSONUtil.toJsonStr(readSheetHolder.getTempCellData()),JSONUtil.toJsonStr(readSheetHolder.getCellMap()), ExceptionUtil.stacktraceToString(exception));
        log.error(errorMsg);
        hasNext = exceptionRead;
    }
    /**
     * 记录连续空行数量
     */
    private int emptyRowNum = 0 ;
    /**
     * 是否继续解析下一行
     * @param context 监听的上下文
     * @return boolean 是否继续
     * @author yzy
     * @date 2022/7/19 17:38
     */
    @Override
    public boolean hasNext(AnalysisContext context) {
        //统计解析到的连续空行数量，超过maxAllowEmptyRowNum行就停止解析
        emptyRowNum = RowTypeEnum.EMPTY.equals(context.readRowHolder().getRowType())?emptyRowNum+1:emptyRowNum>1?emptyRowNum-1:0;
        if(emptyRowNum >= maxAllowEmptyRowNum){
            log.info("现已读取到第{}行，连续读取了{}行空行，停止解析",context.readRowHolder().getRowIndex(),maxAllowEmptyRowNum);
            doAfterAllAnalysed(context);
            return Boolean.FALSE;
        }
        //如果超过允许最大读取行数maxReadRowNum则停止解析
        if(NumberUtil.compare(context.readRowHolder().getRowIndex(),maxReadRowNum)>=0){
            log.info("共解析了{}行，达到最大允许行数，停止解析",maxReadRowNum);
            doAfterAllAnalysed(context);
            return Boolean.FALSE;
        }
        return hasNext;
    }
}
